🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / core / database / src / commonMain / kotlin / org / meshtastic / core / database / DatabaseManager.kt
Displaying Raw • Download
core/database/src/commonMain/kotlin/org/meshtastic/core/database/DatabaseManager.kt 878905aea3776316a2dbed2e441a2c4a16a8eba4 (878905ae) Text, 13.06 KB
T8b949e/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.database
Tff7b72import T7ee787androidx.datastore.core.DataStore
Tff7b72import T7ee787androidx.datastore.preferences.core.Preferences
Tff7b72import T7ee787androidx.datastore.preferences.core.booleanPreferencesKey
Tff7b72import T7ee787androidx.datastore.preferences.core.edit
Tff7b72import T7ee787androidx.datastore.preferences.core.intPreferencesKey
Tff7b72import T7ee787androidx.datastore.preferences.core.longPreferencesKey
Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787kotlinx.coroutines.CancellationException
Tff7b72import T7ee787kotlinx.coroutines.CoroutineScope
Tff7b72import T7ee787kotlinx.coroutines.ExperimentalCoroutinesApi
Tff7b72import T7ee787kotlinx.coroutines.SupervisorJob
Tff7b72import T7ee787kotlinx.coroutines.cancel
Tff7b72import T7ee787kotlinx.coroutines.flow.MutableStateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.SharingStarted
Tff7b72import T7ee787kotlinx.coroutines.flow.StateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.filterNotNull
Tff7b72import T7ee787kotlinx.coroutines.flow.first
Tff7b72import T7ee787kotlinx.coroutines.flow.map
Tff7b72import T7ee787kotlinx.coroutines.flow.stateIn
Tff7b72import T7ee787kotlinx.coroutines.launch
Tff7b72import T7ee787kotlinx.coroutines.sync.Mutex
Tff7b72import T7ee787kotlinx.coroutines.sync.withLock
Tff7b72import T7ee787kotlinx.coroutines.withContext
Tff7b72import T7ee787org.koin.core.annotation.Named
Tff7b72import T7ee787org.koin.core.annotation.Single
Tff7b72import T7ee787org.meshtastic.core.common.util.nowMillis
Tff7b72import T7ee787org.meshtastic.core.di.CoroutineDispatchers
Tff7b72import T7ee787org.meshtastic.core.common.database.DatabaseManager Tff7b72as Te6edf3SharedDatabaseManager
T8b949e/** Manages per-device Room database instances for node data, with LRU eviction. */
Tf0883e@SingleTb4b4b4(Te6edf3binds Tff7b72= Tff7b72[Te6edf3DatabaseProviderTff7b72::Te6edf3classTb4b4b4, Te6edf3SharedDatabaseManagerTff7b72::Te6edf3classTff7b72]Tb4b4b4)
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooManyFunctionsTa5d6ff"Tb4b4b4)
Tf0883e@OptInTb4b4b4(Te6edf3ExperimentalCoroutinesApiTff7b72::Te6edf3classTb4b4b4)
Tff7b72open Tff7b72class T56d364DatabaseManagerTb4b4b4(
Tf0883e@NamedTb4b4b4(Ta5d6ff"Ta5d6ffDatabaseDataStoreTa5d6ff"Tb4b4b4) Tff7b72private Tff7b72val Te6edf3datastoreTb4b4b4: Te6edf3DataStoreTff7b72<Te6edf3PreferencesTff7b72>Tb4b4b4,
Tff7b72private Tff7b72val Te6edf3dispatchersTb4b4b4: Te6edf3CoroutineDispatchersTb4b4b4,
Tb4b4b4) Tb4b4b4: Te6edf3DatabaseProviderTb4b4b4,
Te6edf3SharedDatabaseManager Tb4b4b4{
Tff7b72private Tff7b72val Te6edf3managerScope Tff7b72= Te6edf3CoroutineScopeTb4b4b4(Te6edf3SupervisorJobTb4b4b4(Tb4b4b4) Tff7b72+ Te6edf3dispatchersTb4b4b4.Te6edf3defaultTb4b4b4)
Tff7b72private Tff7b72val Te6edf3mutex Tff7b72= Te6edf3MutexTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3cacheLimitKey Tff7b72= Te6edf3intPreferencesKeyTb4b4b4(Te6edf3DatabaseConstantsTb4b4b4.Te6edf3CACHE_LIMIT_KEYTb4b4b4)
Tff7b72private Tff7b72val Te6edf3legacyCleanedKey Tff7b72= Te6edf3booleanPreferencesKeyTb4b4b4(Te6edf3DatabaseConstantsTb4b4b4.Te6edf3LEGACY_DB_CLEANED_KEYTb4b4b4)
Tff7b72private Tff7b72fun Td2a8fflastUsedKeyTb4b4b4(Te6edf3dbNameTb4b4b4: Tffa657StringTb4b4b4) Tff7b72= Te6edf3longPreferencesKeyTb4b4b4(Ta5d6ff"Ta5d6ffdb_last_used:Tffd700$Te6edf3dbNameTa5d6ff"Tb4b4b4)
Tff7b72override Tff7b72val Te6edf3cacheLimitTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657IntTff7b72> Tff7b72=
Te6edf3datastoreTb4b4b4.Te6edf3data
Tb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTff7b72[Te6edf3cacheLimitKeyTff7b72] Tff7b72?: Te6edf3DatabaseConstantsTb4b4b4.Te6edf3DEFAULT_CACHE_LIMIT Tb4b4b4}
Tb4b4b4.Te6edf3stateInTb4b4b4(Te6edf3managerScopeTb4b4b4, Te6edf3SharingStartedTb4b4b4.Te6edf3EagerlyTb4b4b4, Te6edf3DatabaseConstantsTb4b4b4.Te6edf3DEFAULT_CACHE_LIMITTb4b4b4)
Tff7b72override Tff7b72fun Td2a8ffgetCurrentCacheLimitTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657Int Tff7b72= Te6edf3cacheLimitTb4b4b4.Te6edf3value
Tff7b72override Tff7b72fun Td2a8ffsetCacheLimitTb4b4b4(Te6edf3limitTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3clamped Tff7b72= Te6edf3limitTb4b4b4.Te6edf3coerceInTb4b4b4(Te6edf3DatabaseConstantsTb4b4b4.Te6edf3MIN_CACHE_LIMITTb4b4b4, Te6edf3DatabaseConstantsTb4b4b4.Te6edf3MAX_CACHE_LIMITTb4b4b4)
Te6edf3managerScopeTb4b4b4.Te6edf3launch Tb4b4b4{
Te6edf3datastoreTb4b4b4.Te6edf3edit Tb4b4b4{ Tffa657itTff7b72[Te6edf3cacheLimitKeyTff7b72] Tff7b72= Te6edf3clamped Tb4b4b4}
T8b949e// Enforce asynchronously with current active DB protected
Tff7b72val Te6edf3active Tff7b72=
Te6edf3_currentDbTb4b4b4.Te6edf3valueTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3buildDbNameTb4b4b4(Te6edf3_currentAddressTb4b4b4.Te6edf3valueTb4b4b4) Tb4b4b4} Tff7b72?: Te6edf3DatabaseConstantsTb4b4b4.Te6edf3DEFAULT_DB_NAME
Te6edf3enforceCacheLimitTb4b4b4(Te6edf3activeDbName Tff7b72= Te6edf3activeTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72val Te6edf3dbCache Tff7b72= Te6edf3mutableMapOfTff7b72<Tffa657StringTb4b4b4, Te6edf3MeshtasticDatabaseTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3_currentDb Tff7b72= Te6edf3MutableStateFlowTff7b72<Te6edf3MeshtasticDatabase?Tff7b72>Tb4b4b4(Tff7b72nullTb4b4b4)
T8b949e/**
* The currently active database, built lazily on first access. Room's `onOpen` callback is itself lazy (not invoked
* until the first query), so construction only allocates the builder and connection pool — actual I/O is deferred.
*/
Tff7b72override Tff7b72val Te6edf3currentDbTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3MeshtasticDatabaseTff7b72> Tff7b72=
Te6edf3_currentDb
Tb4b4b4.Te6edf3filterNotNullTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3stateInTb4b4b4(Te6edf3managerScopeTb4b4b4, Te6edf3SharingStartedTb4b4b4.Te6edf3EagerlyTb4b4b4, Te6edf3getOrOpenDatabaseTb4b4b4(Te6edf3DatabaseConstantsTb4b4b4.Te6edf3DEFAULT_DB_NAMETb4b4b4)Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3_currentAddress Tff7b72= Te6edf3MutableStateFlowTff7b72<Tffa657String?Tff7b72>Tb4b4b4(Tff7b72nullTb4b4b4)
Tff7b72val Te6edf3currentAddressTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657String?Tff7b72> Tff7b72= Te6edf3_currentAddress
T8b949e/** Initialize the active database for [address]. */
Tff7b72suspend Tff7b72fun Td2a8ffinitTb4b4b4(Te6edf3addressTb4b4b4: Tffa657String?Tb4b4b4) Tb4b4b4{
Te6edf3switchActiveDatabaseTb4b4b4(Te6edf3addressTb4b4b4)
Tb4b4b4}
T8b949e/**
* Returns a cached [MeshtasticDatabase] or builds a new one for [dbName]. The caller must hold [mutex] when
* modifying [dbCache] concurrently; however, this helper is also used from [currentDb]'s `initialValue` where the
* mutex is not yet relevant (single-threaded construction).
*/
Tff7b72private Tff7b72fun Td2a8ffgetOrOpenDatabaseTb4b4b4(Te6edf3dbNameTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3MeshtasticDatabase Tff7b72=
Te6edf3dbCacheTb4b4b4.Te6edf3getOrPutTb4b4b4(Te6edf3dbNameTb4b4b4) Tb4b4b4{ Te6edf3getDatabaseBuilderTb4b4b4(Te6edf3dbNameTb4b4b4)Tb4b4b4.Te6edf3buildTb4b4b4(Tb4b4b4) Tb4b4b4}
T8b949e/** Switch active database to the one associated with [address]. Serialized via mutex. */
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffswitchActiveDatabaseTb4b4b4(Te6edf3addressTb4b4b4: Tffa657String?Tb4b4b4) Tff7b72= Te6edf3mutexTb4b4b4.Te6edf3withLock Tb4b4b4{
Tff7b72val Te6edf3dbName Tff7b72= Te6edf3buildDbNameTb4b4b4(Te6edf3addressTb4b4b4)
T8b949e// Remember the previously active DB name (any) so we can record its last-used time as well.
Tff7b72val Te6edf3previousDbName Tff7b72= Te6edf3_currentDbTb4b4b4.Te6edf3valueTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3buildDbNameTb4b4b4(Te6edf3_currentAddressTb4b4b4.Te6edf3valueTb4b4b4) Tb4b4b4}
T8b949e// Fast path: no-op if already on this address
Tff7b72if Tb4b4b4(Te6edf3_currentAddressTb4b4b4.Te6edf3value Tff7b72=Tff7b72= Te6edf3address Tff7b72&Tff7b72& Te6edf3_currentDbTb4b4b4.Te6edf3value Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3markLastUsedTb4b4b4(Te6edf3dbNameTb4b4b4)
Tff7b72returnTf0883e@withLock
Tb4b4b4}
T8b949e// Build/open Room DB off the main thread
Tff7b72val Te6edf3db Tff7b72= Te6edf3withContextTb4b4b4(Te6edf3dispatchersTb4b4b4.Te6edf3ioTb4b4b4) Tb4b4b4{ Te6edf3getOrOpenDatabaseTb4b4b4(Te6edf3dbNameTb4b4b4) Tb4b4b4}
T8b949e// Emit the new DB BEFORE closing the old one. flatMapLatest collectors on
T8b949e// currentDb will cancel their in-flight queries on the previous database once
T8b949e// the new value is emitted. Closing the old pool first would race with those
T8b949e// collectors, causing "Connection pool is closed" crashes.
Te6edf3_currentDbTb4b4b4.Te6edf3value Tff7b72= Te6edf3db
Te6edf3_currentAddressTb4b4b4.Te6edf3value Tff7b72= Te6edf3address
Te6edf3markLastUsedTb4b4b4(Te6edf3dbNameTb4b4b4)
T8b949e// Also mark the previous DB as used "just now" so LRU has an accurate, recent timestamp
Te6edf3previousDbNameTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3markLastUsedTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}
T8b949e// Do NOT close the previous DB synchronously here. Even though _currentDb has been
T8b949e// updated, in-flight `withDb` calls may still hold a reference to the old database
T8b949e// (captured before the emission). Closing the connection pool while those queries are
T8b949e// executing causes "Connection pool is closed" crashes. Instead, let LRU eviction
T8b949e// (enforceCacheLimit) handle cleanup — it only runs on databases that are not the
T8b949e// active target and have not been used recently.
T8b949e// Defer LRU eviction so switch is not blocked by filesystem work
Te6edf3managerScopeTb4b4b4.Te6edf3launchTb4b4b4(Te6edf3dispatchersTb4b4b4.Te6edf3ioTb4b4b4) Tb4b4b4{ Te6edf3enforceCacheLimitTb4b4b4(Te6edf3activeDbName Tff7b72= Te6edf3dbNameTb4b4b4) Tb4b4b4}
T8b949e// One-time cleanup: remove legacy DB if present and not active
Te6edf3managerScopeTb4b4b4.Te6edf3launchTb4b4b4(Te6edf3dispatchersTb4b4b4.Te6edf3ioTb4b4b4) Tb4b4b4{ Te6edf3cleanupLegacyDbIfNeededTb4b4b4(Te6edf3activeDbName Tff7b72= Te6edf3dbNameTb4b4b4) Tb4b4b4}
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffSwitched active DB to Tffd700${Te6edf3anonymizeDbNameTb4b4b4(Te6edf3dbNameTb4b4b4)Tffd700}Ta5d6ff for address Tffd700${Te6edf3anonymizeAddressTb4b4b4(Te6edf3addressTb4b4b4)Tffd700}Ta5d6ff" Tb4b4b4}
Tb4b4b4}
T8b949e/**
* Closes and removes a cached database by name. Safe to call even if the database was already closed or not in the
* cache. Does NOT delete the underlying file — the database can be re-opened on next access.
*
* On JVM/Desktop, Room KMP has no auto-close timeout (Android-only API), so idle databases hold open SQLite
* connections (5 per WAL-mode DB) indefinitely until explicitly closed. This method is the primary mechanism for
* releasing those connections when a database is no longer the active target.
*/
Tff7b72private Tff7b72fun Td2a8ffcloseCachedDatabaseTb4b4b4(Te6edf3dbNameTb4b4b4: Tffa657StringTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3removed Tff7b72= Te6edf3dbCacheTb4b4b4.Te6edf3removeTb4b4b4(Te6edf3dbNameTb4b4b4) Tff7b72?: Tff7b72return
Te6edf3runCatching Tb4b4b4{ Te6edf3removedTb4b4b4.Te6edf3closeTb4b4b4(Tb4b4b4) Tb4b4b4}
Tb4b4b4.Te6edf3onFailure Tb4b4b4{ Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffFailed to close cached database Tffd700${Te6edf3anonymizeDbNameTb4b4b4(Te6edf3dbNameTb4b4b4)Tffd700}Ta5d6ff" Tb4b4b4} Tb4b4b4}
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffClosed inactive database Tffd700${Te6edf3anonymizeDbNameTb4b4b4(Te6edf3dbNameTb4b4b4)Tffd700}Ta5d6ff to free connectionsTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72val Te6edf3limitedIo Tff7b72= Te6edf3dispatchersTb4b4b4.Te6edf3ioTb4b4b4.Te6edf3limitedParallelismTb4b4b4(T79c0ff4Tb4b4b4)
T8b949e/** Execute [block] with the current DB instance. */
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4)
Tff7b72override Tff7b72suspend Tff7b72fun Tff7b72<Te6edf3TTff7b72> Td2a8ffwithDbTb4b4b4(Te6edf3blockTb4b4b4: Te6edf3suspend Tb4b4b4(Te6edf3MeshtasticDatabaseTb4b4b4) Tff7b72-Tff7b72> Te6edf3TTb4b4b4)Tb4b4b4: Te6edf3T? Tff7b72= Te6edf3withContextTb4b4b4(Te6edf3limitedIoTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3db Tff7b72= Te6edf3_currentDbTb4b4b4.Te6edf3value Tff7b72?: Tff7b72returnTf0883e@withContext Tff7b72null
Tff7b72val Te6edf3active Tff7b72= Te6edf3buildDbNameTb4b4b4(Te6edf3_currentAddressTb4b4b4.Te6edf3valueTb4b4b4)
Te6edf3markLastUsedTb4b4b4(Te6edf3activeTb4b4b4)
Tff7b72try Tb4b4b4{
Te6edf3blockTb4b4b4(Te6edf3dbTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3CancellationExceptionTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3e T8b949e// Preserve structured concurrency cancellation propagation.
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
T8b949e// If the connection pool was closed between capturing `db` and executing the query
T8b949e// (e.g., during a database switch), retry once with the current DB instance.
Tff7b72if Tb4b4b4(Te6edf3eTb4b4b4.Te6edf3messageTff7b72?.Te6edf3containsTb4b4b4(Ta5d6ff"Ta5d6ffConnection pool is closedTa5d6ff"Tb4b4b4) Tff7b72=Tff7b72= Tff7b72trueTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffwithDb: connection pool closed, retrying with current DBTa5d6ff" Tb4b4b4}
Tff7b72val Te6edf3retryDb Tff7b72= Te6edf3_currentDbTb4b4b4.Te6edf3value Tff7b72?: Tff7b72returnTf0883e@withContext Tff7b72null
Te6edf3blockTb4b4b4(Te6edf3retryDbTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72throw Te6edf3e
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e/** Returns true if a database exists for the given device address. */
Tff7b72override Tff7b72fun Td2a8ffhasDatabaseForTb4b4b4(Te6edf3addressTb4b4b4: Tffa657String?Tb4b4b4)Tb4b4b4: Tffa657Boolean Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3addressTb4b4b4.Te6edf3isNullOrBlankTb4b4b4(Tb4b4b4) Tff7b72|Tff7b72| Te6edf3address Tff7b72=Tff7b72= Ta5d6ff"Ta5d6ffnTa5d6ff"Tb4b4b4) Tff7b72return Tff7b72false
Tff7b72val Te6edf3dbName Tff7b72= Te6edf3buildDbNameTb4b4b4(Te6edf3addressTb4b4b4)
Tff7b72val Te6edf3path Tff7b72= Te6edf3getDatabaseDirectoryTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3resolveTb4b4b4(Ta5d6ff"Tffd700$Te6edf3dbNameTa5d6ff.dbTa5d6ff"Tb4b4b4)
Tff7b72return Te6edf3getFileSystemTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3existsTb4b4b4(Te6edf3pathTb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffmarkLastUsedTb4b4b4(Te6edf3dbNameTb4b4b4: Tffa657StringTb4b4b4) Tb4b4b4{
Te6edf3managerScopeTb4b4b4.Te6edf3launch Tb4b4b4{ Te6edf3datastoreTb4b4b4.Te6edf3edit Tb4b4b4{ Tffa657itTff7b72[Te6edf3lastUsedKeyTb4b4b4(Te6edf3dbNameTb4b4b4)Tff7b72] Tff7b72= Te6edf3nowMillis Tb4b4b4} Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8fflastUsedTb4b4b4(Te6edf3dbNameTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657Long Tb4b4b4{
Tff7b72val Te6edf3key Tff7b72= Te6edf3lastUsedKeyTb4b4b4(Te6edf3dbNameTb4b4b4)
Tff7b72val Te6edf3v Tff7b72= Te6edf3datastoreTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)Tff7b72[Te6edf3keyTff7b72] Tff7b72?: T79c0ff0L
Tff7b72return Tff7b72if Tb4b4b4(Te6edf3v Tff7b72=Tff7b72= T79c0ff0LTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3path Tff7b72= Te6edf3getDatabaseDirectoryTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3resolveTb4b4b4(Ta5d6ff"Tffd700$Te6edf3dbNameTa5d6ff.dbTa5d6ff"Tb4b4b4)
Te6edf3getFileSystemTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3metadataOrNullTb4b4b4(Te6edf3pathTb4b4b4)Tff7b72?.Te6edf3lastModifiedAtMillis Tff7b72?: T79c0ff0L
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3v
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8fflistExistingDbNamesTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Tffa657StringTff7b72> Tb4b4b4{
Tff7b72val Te6edf3dir Tff7b72= Te6edf3getDatabaseDirectoryTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3fs Tff7b72= Te6edf3getFileSystemTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3fsTb4b4b4.Te6edf3existsTb4b4b4(Te6edf3dirTb4b4b4)Tb4b4b4) Tff7b72return Te6edf3emptyListTb4b4b4(Tb4b4b4)
Tff7b72return Te6edf3fsTb4b4b4.Te6edf3listTb4b4b4(Te6edf3dirTb4b4b4)
Tb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3name Tb4b4b4}
Tb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3startsWithTb4b4b4(Te6edf3DatabaseConstantsTb4b4b4.Te6edf3DB_PREFIXTb4b4b4) Tb4b4b4}
Tb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3endsWithTb4b4b4(Ta5d6ff"Ta5d6ff.dbTa5d6ff"Tb4b4b4) Tb4b4b4}
Tb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3removeSuffixTb4b4b4(Ta5d6ff"Ta5d6ff.dbTa5d6ff"Tb4b4b4) Tb4b4b4}
Tb4b4b4.Te6edf3distinctTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffenforceCacheLimitTb4b4b4(Te6edf3activeDbNameTb4b4b4: Tffa657StringTb4b4b4) Tff7b72= Te6edf3mutexTb4b4b4.Te6edf3withLock Tb4b4b4{
Tff7b72val Te6edf3limit Tff7b72= Te6edf3getCurrentCacheLimitTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3all Tff7b72= Te6edf3listExistingDbNamesTb4b4b4(Tb4b4b4)
T8b949e// Only enforce the limit over device-specific DBs; exclude legacy and default DBs
Tff7b72val Te6edf3deviceDbs Tff7b72=
Te6edf3allTb4b4b4.Te6edf3filterNot Tb4b4b4{ Tffa657it Tff7b72=Tff7b72= Te6edf3DatabaseConstantsTb4b4b4.Te6edf3LEGACY_DB_NAME Tff7b72|Tff7b72| Tffa657it Tff7b72=Tff7b72= Te6edf3DatabaseConstantsTb4b4b4.Te6edf3DEFAULT_DB_NAME Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3deviceDbsTb4b4b4.Te6edf3size Tff7b72<Tff7b72= Te6edf3limitTb4b4b4) Tff7b72returnTf0883e@withLock
Tff7b72val Te6edf3usageSnapshot Tff7b72= Te6edf3deviceDbsTb4b4b4.Te6edf3associateWith Tb4b4b4{ Te6edf3lastUsedTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3victims Tff7b72= Te6edf3selectEvictionVictimsTb4b4b4(Te6edf3deviceDbsTb4b4b4, Te6edf3activeDbNameTb4b4b4, Te6edf3limitTb4b4b4, Te6edf3usageSnapshotTb4b4b4)
Te6edf3victimsTb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3name Tff7b72-Tff7b72>
Te6edf3runCatching Tb4b4b4{
T8b949e// runCatching intentional: best-effort cleanup must not abort on cancellation
Te6edf3closeCachedDatabaseTb4b4b4(Te6edf3nameTb4b4b4)
Te6edf3deleteDatabaseTb4b4b4(Te6edf3nameTb4b4b4)
Te6edf3datastoreTb4b4b4.Te6edf3edit Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3removeTb4b4b4(Te6edf3lastUsedKeyTb4b4b4(Te6edf3nameTb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3onSuccess Tb4b4b4{ Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffEvicted cached DB Tffd700${Te6edf3anonymizeDbNameTb4b4b4(Te6edf3nameTb4b4b4)Tffd700}Ta5d6ff" Tb4b4b4} Tb4b4b4}
Tb4b4b4.Te6edf3onFailure Tb4b4b4{ Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffFailed to evict database Tffd700${Te6edf3anonymizeDbNameTb4b4b4(Te6edf3nameTb4b4b4)Tffd700}Ta5d6ff" Tb4b4b4} Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffcleanupLegacyDbIfNeededTb4b4b4(Te6edf3activeDbNameTb4b4b4: Tffa657StringTb4b4b4) Tff7b72= Te6edf3mutexTb4b4b4.Te6edf3withLock Tb4b4b4{
Tff7b72val Te6edf3cleaned Tff7b72= Te6edf3datastoreTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)Tff7b72[Te6edf3legacyCleanedKeyTff7b72] Tff7b72?: Tff7b72false
Tff7b72if Tb4b4b4(Te6edf3cleanedTb4b4b4) Tff7b72returnTf0883e@withLock
Tff7b72val Te6edf3legacy Tff7b72= Te6edf3DatabaseConstantsTb4b4b4.Te6edf3LEGACY_DB_NAME
Tff7b72if Tb4b4b4(Te6edf3legacy Tff7b72=Tff7b72= Te6edf3activeDbNameTb4b4b4) Tb4b4b4{
Te6edf3datastoreTb4b4b4.Te6edf3edit Tb4b4b4{ Tffa657itTff7b72[Te6edf3legacyCleanedKeyTff7b72] Tff7b72= Tff7b72true Tb4b4b4}
Tff7b72returnTf0883e@withLock
Tb4b4b4}
Tff7b72val Te6edf3dir Tff7b72= Te6edf3getDatabaseDirectoryTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3fs Tff7b72= Te6edf3getFileSystemTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3legacyPath Tff7b72= Te6edf3dirTb4b4b4.Te6edf3resolveTb4b4b4(Ta5d6ff"Tffd700$Te6edf3legacyTa5d6ff.dbTa5d6ff"Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3fsTb4b4b4.Te6edf3existsTb4b4b4(Te6edf3legacyPathTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3runCatching Tb4b4b4{
T8b949e// runCatching intentional: best-effort cleanup must not abort on cancellation
Te6edf3closeCachedDatabaseTb4b4b4(Te6edf3legacyTb4b4b4)
Te6edf3deleteDatabaseTb4b4b4(Te6edf3legacyTb4b4b4)
Tb4b4b4}
Tb4b4b4.Te6edf3onSuccess Tb4b4b4{ Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffDeleted legacy DB Tffd700${Te6edf3anonymizeDbNameTb4b4b4(Te6edf3legacyTb4b4b4)Tffd700}Ta5d6ff" Tb4b4b4} Tb4b4b4}
Tb4b4b4.Te6edf3onFailure Tb4b4b4{ Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffFailed to delete legacy database Tffd700${Te6edf3anonymizeDbNameTb4b4b4(Te6edf3legacyTb4b4b4)Tffd700}Ta5d6ff" Tb4b4b4} Tb4b4b4}
Tb4b4b4}
Te6edf3datastoreTb4b4b4.Te6edf3edit Tb4b4b4{ Tffa657itTff7b72[Te6edf3legacyCleanedKeyTff7b72] Tff7b72= Tff7b72true Tb4b4b4}
Tb4b4b4}
T8b949e/** Closes all open databases and cancels background work. */
Tff7b72fun Td2a8ffcloseTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3managerScopeTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Te6edf3dbCacheTb4b4b4.Te6edf3valuesTb4b4b4.Te6edf3forEach Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3closeTb4b4b4(Tb4b4b4) Tb4b4b4}
Te6edf3dbCacheTb4b4b4.Te6edf3clearTb4b4b4(Tb4b4b4)
Te6edf3_currentDbTb4b4b4.Te6edf3value Tff7b72= Tff7b72null
Tb4b4b4}
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.1s